#ifndef NETWORK_H
#define NETWORK_H

#include "GameObject.h"
#include "Player.h"

#include "PacketEnumerations.h"
#include "RakNetworkFactory.h"
#include "RakClientInterface.h"
#include "RakServerInterface.h"
#include "NetworkTypes.h"
#include "BitStream.h"
#include "NetworkIDGenerator.h"

#include <vector>
#include <map>
#include <string>

typedef std::string String;

//Custom packet type enumerations
enum {
    ID_NETWORKOBJECT_UPDATE = ID_USER_PACKET_ENUM,
    ID_CREATE_NETWORKOBJECT,
    ID_CLIENT_INPUT,
    ID_CHANGE_VEL,
    ID_START_STOP,
    ID_SPAWN
    //...
};

class CGame;

class CNetworkObject;

//TODO: Work out why can't have common base class and then pick derived client or server during
//		runtime. Factors to consider include host\join functions, server acting as client, ...
//		Also, check CNetIDGenerator::IsNetworkIDAuthority() definition.
//		Could have client\server definition in Game object instead?
//			Client and Server objects in Game class?
//		Could also not have common base class if not necessary and simply have two different classes
//		Think about (and see atm) how components of the game will need to determine whether client or server (e.g., Input).

//TODO: Work out how to plug in level 2 code to handle custom packet types.

class CMultiplayer
{
    
    public:
        
        CMultiplayer(CGame* pGame);
        
        void host(unsigned short iMaxPlayers, unsigned short iPort);
        void join(const char* cpHostIP, unsigned short iServerPort, unsigned short iClientPort);
        void checkForPackets();
        void update();
        void disconnect();
        
        bool isServer()      { return m_bIsServer; }
        
        //CNetworkObject* addObject(CNetworkObject* pObject)      { m_pObjects.push_back(pObject); return m_pObjects.back(); }
        
        //TODO: Check if these are really needed
        RakServerInterface* getServer()         { return m_pServer; }
        RakClientInterface* getClient()         { return m_pClient; }
        
        CNetworkObject* addNetworkObject(String sType);
        //TODO: Check where or if this is used
        CNetworkObject* getNetworkObject(int index)         { return m_pObjects[index]; }
        
        //Returns a pointer to the Player instance associated with a given PlayerID, or
		//the local player if we are a client
        //TODO: Think about whether clients may need a map of players too.
        //NOTE: Passing no argument leads to a search for UNASSIGNED_PLAYER _ID, which
		//		could never have been added to the map.
        CPlayer* getPlayer(PlayerID id = UNASSIGNED_PLAYER_ID)
        {
			if (m_bIsServer) {
				//Search the map of players
				PlayerMapIterator itPlayer = m_mPlayers.find(id);
				if (itPlayer == m_mPlayers.end())
					return 0; 		//If no match found, return failed.
				//Return a pointer to the found Player instance
				return itPlayer->second;
			}
			
			//We are a client, so return the local Player instance
			return m_pPlayer;
		}
        
    private:
        
        void updateObjectFromBitStream(RakNet::BitStream& bsData);
        
        //Responses to core multiplayer messages
        void onClientConnect(PlayerID pidPlayer);
        //void onServerDisconnect(...);
        
        CGame* m_pGame;
        
        RakServerInterface* m_pServer;
        RakClientInterface* m_pClient;
        
        bool m_bIsServer;
        
        typedef std::map<PlayerID, CPlayer*> PlayerMap;
        typedef std::map<PlayerID, CPlayer*>::iterator PlayerMapIterator;
        PlayerMap m_mPlayers;			//If server
        CPlayer* m_pPlayer;				//If client
        
        //Array of all network game objects
        //NOTE: These can be retrieved given a NetworkID via
		//		(CNetworkObject*) NetworkIDGenerator::GET_OBJECT_FROM_ID(nidObjectID);.
        std::vector<CNetworkObject*> m_pObjects;
        
};

class CNetIDGenerator : public NetworkIDGenerator
{
    
    public:
        
        virtual bool IsNetworkIDAuthority (void) const;
        virtual bool RequiresSetParent (void) const         { return true; }
    
};

class CNetworked
//class CNetworkObject : public CGameObject
{
    
    public:
        
        CNetworked()
        //CNetworkObject()
        {
            
            m_NetIDGenerator.SetParent((void*)this);
            
        }
        
        NetworkIDGenerator& getNetIDGenerator()      { return m_NetIDGenerator; }
        
        //virtual void updateFromBitStream(RakNet::BitStream bsData);
        
        //virtual void generateUpdateBitStream(RakNet::BitStream& bsObjectData);
        
    protected:
        
        CNetIDGenerator m_NetIDGenerator;
        
    
};

class CNetworkObject : public CNetworked, public CGameObject
{
    
    public:
        
        CNetworkObject() : CNetworked(), CGameObject()
        {
            
            //NOTE: We needed to do this again because multiple inheritance does funny things with the addresses
            m_NetIDGenerator.SetParent((void*)this);
            
        }
        
        virtual void updateFromBitStream(RakNet::BitStream bsData);
        
        virtual void generateUpdateBitStream(RakNet::BitStream& bsObjectData);
        
    private:
        
        
    
};

#endif //NETWORK_H
